AWS SAM でビルドは成功しているが、ビルド前の成果物がデプロイされる時はファイルが使い分けられているか確認しよう
こんにちは! AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。
先日、SAM を使ってビルド処理を行う必要がありました。ビルドは成功しているものの、ビルド前の成果物がデプロイされ詰まったためブログにします。
問題
早速ですが問題です。
以下の構成で Lambda Layer を作成しているとします。
takakuni@ samblog % tree -L4 -a
.
├── .aws-sam
│ ├── build
│ │ ├── Requests
│ │ │ └── python
│ │ └── template.yaml
│ └── build.toml
├── layers
│ └── Requests
│ └── requirements.txt
├── samconfig.yaml
└── template.yaml
7 directories, 5 files
template.yaml
はシンプルに Layer を作るだけにしています。
Transform: AWS::Serverless-2016-10-31
Description: SAM Template
Resources:
Requests:
Type: AWS::Serverless::LayerVersion
Metadata:
BuildMethod: python3.12
SamResourceId: Requests
Properties:
LayerName: 'Requests'
Description: 'Requests'
CompatibleArchitectures:
- x86_64
CompatibleRuntimes:
- python3.12
ContentUri: './layers/Requests'
RetentionPolicy: Delete
samconfig.yaml
の template_file
には何が入るでしょうか?
template.yaml
.aws-sam/build/template.yaml
version: 0.1
default:
build:
parameters:
cached: false
debug: true
mount_with: READ
takakuni:
deploy:
parameters:
stack_name: 'lambda-layer-sample'
+ template_file: '?'
on_failure: 'ROLLBACK'
max_wait_duration: 60
capabilities:
- 'CAPABILITY_IAM'
- 'CAPABILITY_NAMED_IAM'
profile: 'takakuni'
resolve_s3: true
s3_prefix: 'samsam'
正解は 2 番の .aws-sam/build/template.yaml
です。
ContentUri
AWS::Serverless::LayerVersion の ContentUri をみてみましょう。
「テンプレートは sam build の後に sam deploy または sam package を含むワークフローを通過しなければならない。」と記載されていますね。
これは一体どういうことなのでしょうか。
ContentUri
Amazon S3 Uri, path to local folder, or LayerContent object of the layer code.If an Amazon S3 Uri or LayerContent object is provided, The Amazon S3 object referenced must be a valid ZIP archive that contains the contents of an Lambda layer.
If a path to a local folder is provided, for the content to be transformed properly the template must go through the workflow that includes sam build followed by either sam deploy or sam package. By default, relative paths are resolved with respect to the AWS SAM template's location.
Type: String | LayerContent
Required: Yes
AWS CloudFormation compatibility: This property is similar to the Content property of an AWS::Lambda::LayerVersion resource. The nested Amazon S3 properties are named differently.
ビルドした成果物
sam build
は Python の場合、先ほどの ContentUri で指定したディレクトリの requirements.txt
を参照し pip install
が行われます。最終的な成果物はデフォルトで .aws-sam/build
に保管されます。
takakuni@HL01556 samblog % sam build -t template.yaml --debug
2025-01-01 22:10:15,618 | Config file location: /Users/takakuni/Documents/sample/samblog/samconfig.yaml
2025-01-01 22:10:15,620 | Loading configuration values from [default.['build'].parameters] (env.command_name.section) in config file at
'/Users/takakuni/Documents/sample/samblog/samconfig.yaml'...
2025-01-01 22:10:15,620 | Configuration values successfully loaded.
2025-01-01 22:10:15,621 | Configuration values are: {'cached': False, 'debug': True, 'mount_with': 'READ'}
2025-01-01 22:10:15,635 | OSError occurred while reading TOML file: [Errno 2] No such file or directory: '/Users/takakuni/Documents/sample/samblog/samconfig.toml'
2025-01-01 22:10:15,635 | Using config file: samconfig.toml, config environment: default
2025-01-01 22:10:15,636 | Expand command line arguments to:
2025-01-01 22:10:15,636 | --template_file=/Users/takakuni/Documents/sample/samblog/template.yaml --mount_with=READ --build_dir=.aws-sam/build --cache_dir=.aws-sam/cache
2025-01-01 22:10:15,651 | 'build' command is called
2025-01-01 22:10:15,653 | No Parameters detected in the template
2025-01-01 22:10:15,665 | Sam customer defined id is more priority than other IDs. Customer defined id for resource Requests is Requests
2025-01-01 22:10:15,665 | 0 stacks found in the template
2025-01-01 22:10:15,666 | No Parameters detected in the template
2025-01-01 22:10:15,675 | Sam customer defined id is more priority than other IDs. Customer defined id for resource Requests is Requests
2025-01-01 22:10:15,676 | 1 resources found in the stack
2025-01-01 22:10:15,676 | --base-dir is not presented, adjusting uri ./layers/Requests relative to /Users/takakuni/Documents/sample/samblog/template.yaml
2025-01-01 22:10:15,688 | 1 resources found in the stack
2025-01-01 22:10:15,689 | Instantiating build definitions
2025-01-01 22:10:15,691 | Same Layer build definition found, adding layer (Previous: LayerBuildDefinition(Requests,
/Users/takakuni/Documents/sample/samblog/layers/Requests, , 60f34ec0-e210-4832-91ae-6ba691764a05, python3.12, ['python3.12'], x86_64, {}), Current:
LayerBuildDefinition(Requests, /Users/takakuni/Documents/sample/samblog/layers/Requests, , 361efbcc-56ec-45fb-bf37-2d6826866b3f, python3.12, ['python3.12'], x86_64, {}),
Layer: <samcli.lib.providers.provider.LayerVersion object at 0x105bf6d90>)
2025-01-01 22:10:15,692 | Building layer 'Requests'
2025-01-01 22:10:15,693 | Loading workflow module 'aws_lambda_builders.workflows'
2025-01-01 22:10:15,694 | Registering workflow 'CustomMakeBuilder' with capability 'Capability(language='provided', dependency_manager=None, application_framework=None)'
2025-01-01 22:10:15,695 | Registering workflow 'DotnetCliPackageBuilder' with capability 'Capability(language='dotnet', dependency_manager='cli-package',
application_framework=None)'
2025-01-01 22:10:15,696 | Registering workflow 'GoModulesBuilder' with capability 'Capability(language='go', dependency_manager='modules', application_framework=None)'
2025-01-01 22:10:15,697 | Registering workflow 'JavaGradleWorkflow' with capability 'Capability(language='java', dependency_manager='gradle', application_framework=None)'
2025-01-01 22:10:15,698 | Registering workflow 'JavaMavenWorkflow' with capability 'Capability(language='java', dependency_manager='maven', application_framework=None)'
2025-01-01 22:10:15,699 | Registering workflow 'NodejsNpmBuilder' with capability 'Capability(language='nodejs', dependency_manager='npm', application_framework=None)'
2025-01-01 22:10:15,700 | Registering workflow 'NodejsNpmEsbuildBuilder' with capability 'Capability(language='nodejs', dependency_manager='npm-esbuild',
application_framework=None)'
2025-01-01 22:10:15,702 | Registering workflow 'PythonPipBuilder' with capability 'Capability(language='python', dependency_manager='pip', application_framework=None)'
2025-01-01 22:10:15,703 | Registering workflow 'RubyBundlerBuilder' with capability 'Capability(language='ruby', dependency_manager='bundler', application_framework=None)'
2025-01-01 22:10:15,704 | Registering workflow 'RustCargoLambdaBuilder' with capability 'Capability(language='rust', dependency_manager='cargo', application_framework=None)'
2025-01-01 22:10:15,716 | Found workflow 'PythonPipBuilder' to support capabilities 'Capability(language='python', dependency_manager='pip', application_framework=None)'
2025-01-01 22:10:15,916 | Running workflow 'PythonPipBuilder'
2025-01-01 22:10:15,917 | Running PythonPipBuilder:ResolveDependencies
2025-01-01 22:10:16,154 | calling pip download -r /Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt --dest
/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik --exists-action i
2025-01-01 22:10:16,740 | pip stdout: b'Collecting requests (from -r /Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n Using cached
requests-2.32.3-py3-none-any.whl.metadata (4.6 kB)\nCollecting charset-normalizer<4,>=2 (from requests->-r
/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n Using cached
charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl.metadata (35 kB)\nCollecting idna<4,>=2.5 (from requests->-r
/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n Using cached idna-3.10-py3-none-any.whl.metadata (10 kB)\nCollecting
urllib3<3,>=1.21.1 (from requests->-r /Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n Using cached
urllib3-2.3.0-py3-none-any.whl.metadata (6.5 kB)\nCollecting certifi>=2017.4.17 (from requests->-r
/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt (line 1))\n Using cached certifi-2024.12.14-py3-none-any.whl.metadata (2.3 kB)\nUsing cached
requests-2.32.3-py3-none-any.whl (64 kB)\nUsing cached certifi-2024.12.14-py3-none-any.whl (164 kB)\nUsing cached charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl
(196 kB)\nUsing cached idna-3.10-py3-none-any.whl (70 kB)\nUsing cached urllib3-2.3.0-py3-none-any.whl (128 kB)\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/requests-2.32.3-py3-none-any.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/certifi-2024.12.14-py3-none-any.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/idna-3.10-py3-none-any.whl\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/urllib3-2.3.0-py3-none-any.whl\nSuccessfully downloaded requests certifi charset-normalizer idna urllib3\n'
2025-01-01 22:10:16,743 | pip stderr: b"WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.\nPlease see
https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.\nTo avoid this problem you can invoke Python with '-m pip' instead of running pip directly.\n"
2025-01-01 22:10:16,744 | Full dependency closure: {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel), certifi==2024.12.14(wheel)}
2025-01-01 22:10:16,744 | initial compatible: {idna==3.10(wheel), certifi==2024.12.14(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel)}
2025-01-01 22:10:16,745 | initial incompatible: {charset-normalizer==3.4.1(wheel)}
2025-01-01 22:10:16,745 | Downloading missing wheels: {charset-normalizer==3.4.1(wheel)}
2025-01-01 22:10:16,746 | calling pip download --only-binary=:all: --no-deps --platform manylinux2014_x86_64 --implementation cp --abi cp312 --dest
/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik charset-normalizer==3.4.1
2025-01-01 22:10:17,218 | pip stdout: b'Collecting charset-normalizer==3.4.1\n Using cached
charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (35 kB)\nUsing cached
charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (145 kB)\nSaved
/private/var/folders/39/6fw1g3mx65jbvjcr74h2s27w0000gn/T/tmpakwcx6ik/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\nSuccessfully downloaded
charset-normalizer\n'
2025-01-01 22:10:17,219 | pip stderr: b"WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.\nPlease see
https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.\nTo avoid this problem you can invoke Python with '-m pip' instead of running pip directly.\n"
2025-01-01 22:10:17,220 | compatible wheels after second download pass: {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel),
certifi==2024.12.14(wheel)}
2025-01-01 22:10:17,220 | Build missing wheels from sdists (C compiling True): set()
2025-01-01 22:10:17,221 | compatible after building wheels (no C compiling): {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel),
certifi==2024.12.14(wheel)}
2025-01-01 22:10:17,221 | Build missing wheels from sdists (C compiling False): set()
2025-01-01 22:10:17,222 | compatible after building wheels (C compiling): {charset-normalizer==3.4.1(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel), idna==3.10(wheel),
certifi==2024.12.14(wheel)}
2025-01-01 22:10:17,222 | Final compatible: {idna==3.10(wheel), charset-normalizer==3.4.1(wheel), certifi==2024.12.14(wheel), urllib3==2.3.0(wheel), requests==2.32.3(wheel)}
2025-01-01 22:10:17,223 | Final incompatible: {charset-normalizer==3.4.1(wheel)}
2025-01-01 22:10:17,223 | Final missing wheels: set()
2025-01-01 22:10:17,239 | PythonPipBuilder:ResolveDependencies succeeded
2025-01-01 22:10:17,240 | Running PythonPipBuilder:CopySource
2025-01-01 22:10:17,241 | Copying source file (/Users/takakuni/Documents/sample/samblog/layers/Requests/requirements.txt) to destination
(/Users/takakuni/Documents/sample/samblog/.aws-sam/build/Requests/python/requirements.txt)
2025-01-01 22:10:17,242 | PythonPipBuilder:CopySource succeeded
2025-01-01 22:10:17,243 | Sam customer defined id is more priority than other IDs. Customer defined id for resource Requests is Requests
2025-01-01 22:10:17,243 | 1 resources found in the stack
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
2025-01-01 22:10:17,245 | Telemetry endpoint configured to be https://aws-serverless-tools-telemetry.us-west-2.amazonaws.com/metrics
2025-01-01 22:10:17,371 | Telemetry endpoint configured to be https://aws-serverless-tools-telemetry.us-west-2.amazonaws.com/metrics
2025-01-01 22:10:17,372 | Sending Telemetry: {'metrics': [{'commandRun': {'requestId': '7f6f6626-6fa7-41de-8e93-6bcf88dd2e78', 'installationId':
'f860202e-7e45-47ad-bc4f-27c0add960f5', 'sessionId': 'd2ecc91d-9c0a-4ed8-90d9-9f23e65de34c', 'executionEnvironment': 'CLI', 'ci': False, 'pyversion': '3.8.20', 'samcliVersion':
'1.126.0', 'awsProfileProvided': False, 'debugFlagProvided': True, 'region': '', 'commandName': 'sam build', 'metricSpecificAttributes': {'projectType': 'CFN', 'gitOrigin': None,
'projectName': '9b56b0810290e646069522dc410faab3274b3b5661821ad30efefa48e2160bd2', 'initialCommit': None}, 'duration': 1609, 'exitReason': 'success', 'exitCode': 0}}]}
2025-01-01 22:10:17,372 | Unable to find Click Context for getting session_id.
2025-01-01 22:10:17,375 | Sending Telemetry: {'metrics': [{'events': {'requestId': '1fc0c680-a2ff-4841-a5e1-7d3f947b5bc9', 'installationId': 'f860202e-7e45-47ad-bc4f-27c0add960f5',
'sessionId': 'd2ecc91d-9c0a-4ed8-90d9-9f23e65de34c', 'executionEnvironment': 'CLI', 'ci': False, 'pyversion': '3.8.20', 'samcliVersion': '1.126.0', 'commandName': 'sam build',
'metricSpecificAttributes': {'events': [{'event_name': 'SamConfigFileExtension', 'event_value': '.yaml', 'thread_id': '43a30774f14848e39283b9dfe91609e2', 'time_stamp': '2025-01-01
13:10:15.618', 'exception_name': None}, {'event_name': 'SamConfigFileExtension', 'event_value': '.toml', 'thread_id': '108882b733c24f7a93aee7c989613760', 'time_stamp': '2025-01-01
13:10:15.635', 'exception_name': None}, {'event_name': 'BuildWorkflowUsed', 'event_value': 'python-pip', 'thread_id': '626eafa3a0c542519d2310a17ba50ede', 'time_stamp': '2025-01-01
13:10:15.693', 'exception_name': None}]}}}]}
2025-01-01 22:10:17,728 | HTTPSConnectionPool(host='aws-serverless-tools-telemetry.us-west-2.amazonaws.com', port=443): Read timed out. (read timeout=0.1)
2025-01-01 22:10:17,782 | HTTPSConnectionPool(host='aws-serverless-tools-telemetry.us-west-2.amazonaws.com', port=443): Read timed out. (read timeout=0.1)
デプロイ
sam build が成功したので「テンプレートは sam build の後に sam deploy または sam package を含むワークフローを通過しなければならない。」のデプロイに進みます。
さきほどの samconfig.yaml
に戻ります。試しに誤りの template.yaml
を指定するとします。
version: 0.1
default:
build:
parameters:
cached: false
debug: true
mount_with: READ
takakuni:
deploy:
parameters:
stack_name: 'lambda-layer-sample'
template_file: 'template.yaml'
on_failure: 'ROLLBACK'
max_wait_duration: 60
capabilities:
- 'CAPABILITY_IAM'
- 'CAPABILITY_NAMED_IAM'
profile: 'takakuni'
resolve_s3: true
s3_prefix: 'samsam'
この場合レイヤーに登録されるのは ContentUri に指定した ./layers/Requests
の内容(ビルド前の requirements.txt
のみ)になります。
takakuni@ samblog % tree -L4 -a
.
├── .aws-sam
│ ├── build
│ │ ├── Requests
│ │ │ └── python
│ │ └── template.yaml
│ └── build.toml
├── layers
│ └── Requests
│ └── requirements.txt
├── samconfig.yaml
└── template.yaml
7 directories, 5 files
つまり、この ContentUri は
sam build
コマンドの時はビルド前の状態を指定しておくことsam deploy
コマンドの時はビルド後の状態を指定しておくこと
の 2 つの要件が必要になります。これが「テンプレートは sam build の後に sam deploy または sam package を含むワークフローを通過しなければならない。」です。
コマンド実行時のファイル使い分け
先ほどの sam build
、sam deploy
コマンドを 1 つのファイルの ContentUri で使い分けるのは難しいです。
そのため、コマンド別にファイルを使い分けます。
template.yaml
と .aws-sam/build/template.yaml
の中身を見てみましょう。 ContentUri が若干違いますね。
Transform: AWS::Serverless-2016-10-31
Description: SAM Template
Resources:
Requests:
Type: AWS::Serverless::LayerVersion
Metadata:
BuildMethod: python3.12
SamResourceId: Requests
Properties:
LayerName: 'Requests'
Description: 'Requests'
CompatibleArchitectures:
- x86_64
CompatibleRuntimes:
- python3.12
ContentUri: './layers/Requests'
RetentionPolicy: Delete
.aws-sam/build/template.yaml
はビルドした成果物のパスを見ています。
Transform: AWS::Serverless-2016-10-31
Description: SAM Template
Resources:
Requests:
Type: AWS::Serverless::LayerVersion
Metadata:
BuildMethod: python3.12
SamResourceId: Requests
Properties:
LayerName: Requests
Description: Requests
CompatibleArchitectures:
- x86_64
CompatibleRuntimes:
- python3.12
+ ContentUri: Requests
RetentionPolicy: Delete
よって、samconfig.yaml
には .aws-sam/build/template.yaml
を指定します。
version: 0.1
default:
build:
parameters:
cached: false
debug: true
mount_with: READ
takakuni:
deploy:
parameters:
stack_name: 'lambda-layer-sample'
template_file: '.aws-sam/build/template.yaml'
on_failure: 'ROLLBACK'
max_wait_duration: 60
capabilities:
- 'CAPABILITY_IAM'
- 'CAPABILITY_NAMED_IAM'
profile: 'takakuni'
resolve_s3: true
s3_prefix: 'samsam'
sam build
が参照するコマンドを見てみます。 --template-file
で指定できますがデフォルトでは template.yaml|yml
が参照されます。
--template-file, --template, -t PATH
The path and file name of AWS SAM template file [default: template.[yaml|yml]]. This option is not compatible with --hook-name.
相関関係を表した図が以下になります。
なぜ本エントリを書くきっかけになったのか
sam deploy
で template.yaml を指定したいケースは、今回のようなコマンドごとにテンプレートの使い分けが求められるようです。
まとめ
以上、「AWS SAM でビルドは成功しているが、ビルド前の成果物がデプロイされる時はファイルが使い分けられているか確認しよう」でした。
ビルドした成果物がレイヤーにうまく登録されいないケースは、ありがちだと思うので備忘録的に書いてみました。
クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!